/************************************
 * Package: RGcpp					*
 * Authors: Rui Eduardo Gouveia Gil	*
 * Date: 2010						*
 ************************************/
#include "rgcpp/utils/ZipFile.h"
#include <boost/filesystem.hpp>

namespace rgcpp
{

ZipFile::ZipFile() : archive(NULL), fileStream(NULL)
{

}

ZipFile::ZipFile(std::string archivePath) : archive(NULL), fileStream(NULL)
{
	this->archivePath=archivePath;

	if(boost::filesystem::exists(archivePath))
		this->open(archivePath);
	else
		this->create(archivePath);
}

ZipFile::~ZipFile()
{
	this->close();
}

void ZipFile::create(std::string archivePath)
{
	if(archive)
		close();

	int errorp;

	this->archivePath=archivePath;
	archive = zip_open(archivePath.c_str(),ZIP_CREATE,&errorp);

	if(!archive)
	{
		Throw(ZIPException("Unable to create zip archive!!!"));
	}
}

void ZipFile::open(std::string archivePath)
{
	if(archive)
		close();

	int errorp;

	this->archivePath=archivePath;
	archive = zip_open(archivePath.c_str(),ZIP_CHECKCONS,&errorp);

	if(!archive)
	{
		Throw(ZIPException(std::string("Unable to open zip archive!!! (")+archivePath+")"));
	}
}

bool ZipFile::isValid()
{
	return (archive!=NULL);
}

void ZipFile::sync()
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	if(zip_close(archive))
	{
		Throw(ZIPException("Unable to sync zip archive!!!"));
	}

	archive = NULL;

	int errorp;

	archive = zip_open(archivePath.c_str(),ZIP_CHECKCONS,&errorp);

	if(!archive)
	{
		Throw(ZIPException("Unable to sync zip archive!!!"));
	}
}

void ZipFile::close()
{
	if(!archive)
		return;

	if(zip_close(archive))
	{
		Throw(ZIPException("Unable to close zip archive!!!"));
	}

	archive = NULL;
}

void ZipFile::addFile(std::string fileName, std::string filePath)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	struct zip_source* source = zip_source_file(archive,filePath.c_str(),0,0);

	if(!source)
	{
		Throw(ZIPException("Invalid source file!!!"));
	}

	if(zip_add(archive,fileName.c_str(),source)==-1)
	{
		Throw(ZIPException("Unable to add file to zip archive!!!"));
	}
}

bool ZipFile::isFile(std::string fileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	return (zip_name_locate(archive,fileName.c_str(),0) != -1);
}

void ZipFile::replaceFile(std::string fileName, std::string filePath)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	this->replaceFile(this->getFileIndex(fileName),filePath);
}

void ZipFile::replaceFile(unsigned int index, std::string filePath)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	struct zip_source* source = zip_source_file(archive,filePath.c_str(),0,0);

	if(!source)
	{
		Throw(ZIPException("Invalid source file!!!"));
	}

	if(zip_replace(archive,index,source)==-1)
	{
		zip_source_free(source);
		Throw(ZIPException("Unable to replace file to zip archive!!!"));
	}
}

void ZipFile::renameFile(std::string fileName, std::string newFileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	this->renameFile(this->getFileIndex(fileName),newFileName);
}

void ZipFile::renameFile(unsigned int index, std::string newFileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	if(zip_rename(archive,index,newFileName.c_str()))
	{
		Throw(ZIPException("Unable to rename file!!!"));
	}
}

void ZipFile::deleteFile(std::string fileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	this->deleteFile(this->getFileIndex(fileName));
}

void ZipFile::deleteFile(unsigned int index)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	if(zip_delete(archive,index))
	{
		Throw(ZIPException("Unable to delete file!!!"));
	}
}

unsigned int ZipFile::getFileCount()
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	return zip_get_num_files(archive);
}

std::vector<std::string> ZipFile::listArchive()
{
	std::vector<std::string> vec;
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	const char* name;
	int length = zip_get_num_files(archive);

	for(int i=0; i<length; i++)
	{
		name = zip_get_name(archive,i,ZIP_FL_UNCHANGED);

		if(name)
			vec.push_back(name);
	}

	return vec;
}

std::string ZipFile::getFileName(unsigned int index)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	const char* name = zip_get_name(archive,index,ZIP_FL_UNCHANGED);

	if(!name)
	{
		Throw(ZIPException("Invalid index!!!"));
	}

	return std::string(name);
}

unsigned int ZipFile::getFileIndex(std::string fileName, char flag)
{
	int index = zip_name_locate(archive,fileName.c_str(),0);

	if(index==-1)
	{
		if(flag==ZIP_NONE)
		{
			Throw(ZIPException("Invalid name!!!"));
		}

		index = zip_name_locate(archive,fileName.c_str(),ZIP_FL_NOCASE);

		if(index==-1)
		{
			if(flag==ZIP_NOCASE)
			{
				Throw(ZIPException("Invalid name!!!"));
			}

			index = zip_name_locate(archive,fileName.c_str(),ZIP_FL_NODIR);

			if(index==-1)
			{
				Throw(ZIPException("Invalid name!!!"));
			}
		}
	}

	return index;
}

std::string ZipFile::getCompleteFileName(std::string fileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	const char* name = zip_get_name(archive,this->getFileIndex(fileName),ZIP_FL_UNCHANGED);

	if(!name)
	{
		Throw(ZIPException("Invalid name!!!"));
	}

	return std::string(name);
}

int ZipFile::getFileSize(std::string fileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	struct zip_stat sb;

	if(zip_stat(archive,fileName.c_str(),ZIP_FL_UNCHANGED,&sb)==-1)
	{
		Throw(ZIPException("Invalid name!!!"));
	}

	return sb.size;
}

int ZipFile::getFileSize(unsigned int index)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	struct zip_stat sb;

	if(zip_stat_index(archive,index,ZIP_FL_UNCHANGED,&sb)==-1)
	{
		Throw(ZIPException("Invalid index!!!"));
	}

	return sb.size;
}

int ZipFile::getFileDiskSize(std::string fileName)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	struct zip_stat sb;

	if(zip_stat(archive,fileName.c_str(),ZIP_FL_UNCHANGED,&sb)==-1)
	{
		Throw(ZIPException("Invalid name!!!"));
	}

	return sb.comp_size;
}

int ZipFile::getFileDiskSize(unsigned int index)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	struct zip_stat sb;

	if(zip_stat_index(archive,index,ZIP_FL_UNCHANGED,&sb)==-1)
	{
		Throw(ZIPException("Invalid index!!!"));
	}

	return sb.comp_size;
}

void ZipFile::openFileStream(std::string fileName)
{
	if(!fileStream)
		closeFileStream();

	fileStream = zip_fopen(archive,fileName.c_str(),ZIP_FL_UNCHANGED);

	if(!fileStream)
	{
		Throw(ZIPException("Unable to open file!!!"));
	}
}

void ZipFile::openFileStream(unsigned int index)
{
	if(!fileStream)
		closeFileStream();

	fileStream = zip_fopen_index(archive,index,ZIP_FL_UNCHANGED);

	if(!fileStream)
	{
		Throw(ZIPException("Invalid index!!!"));
	}
}

int ZipFile::readFromFile(char* buffer, unsigned int size)
{
	if(!fileStream)
	{
		Throw(ZIPException("Invalid file archive!!!"));
	}

	return zip_fread(fileStream,buffer,size);
}

void ZipFile::closeFileStream()
{
	if(!fileStream)
		return;

	if(zip_fclose(fileStream))
	{
		Throw(ZIPException("Unable to close file!!!"));
	}

	fileStream = NULL;
}

void ZipFile::extractFile(std::string fileName, std::string dirPath)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	if(!dirPath.size() && dirPath[dirPath.size()-1]!='/')
		dirPath += "/";

	if(!fileName.empty() && fileName[fileName.size()-1] == '/')
	{
		std::string fullDirPath = dirPath + fileName.substr(0,fileName.find_last_of('/')+1);
		if(!boost::filesystem::exists(fullDirPath))
			boost::filesystem::create_directories(fullDirPath);
		return;
	}

	if(fileName.find_last_of('/') != std::string::npos)
	{
		std::string fullDirPath = dirPath + fileName.substr(0,fileName.find_last_of('/')+1);
		if(!boost::filesystem::exists(fullDirPath))
			boost::filesystem::create_directories(fullDirPath);
	}

	std::ofstream file((dirPath+fileName).c_str(),std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);

	if (!file.good())
	{
		Throw(ZIPException(std::string("Enable to open destination file!!!")+(dirPath+fileName)));
	}

	int size = getFileSize(fileName);

	fileStream = zip_fopen(archive,fileName.c_str(),ZIP_FL_UNCHANGED);

	if(!fileStream)
	{
		Throw(ZIPException("Unable to open file!!!"));
	}

	int length;
	char buffer[1024];

	do
	{
		if(size<1024)
			length = zip_fread(fileStream,buffer,size);
		else
			length = zip_fread(fileStream,buffer,1024);

		if(length<0)
		{
			Throw(ZIPException("Corrupted file!!!"));
		}

		size -= length;
		file.write(buffer,length);
	}while(size);

	file.close();

	if(zip_fclose(fileStream))
	{
		Throw(ZIPException("Unable to close file!!!"));
	}

	fileStream = NULL;
}

void ZipFile::extractFileToFile(std::string fileName, std::string filePath)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	std::ofstream file(filePath.c_str(),std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);

	if (!file.good())
	{
		Throw(ZIPException(std::string("Enable to open destination file!!!")+filePath));
		Throw(ZIPException("Enable to open destination file!!!"));
	}

	int size = getFileSize(fileName);

	fileStream = zip_fopen(archive,fileName.c_str(),ZIP_FL_UNCHANGED);

	if(!fileStream)
	{
		Throw(ZIPException("Unable to open file!!!"));
	}

	int length;
	char buffer[1024];

	do
	{
		if(size<1024)
			length = zip_fread(fileStream,buffer,size);
		else
			length = zip_fread(fileStream,buffer,1024);

		if(length<0)
		{
			Throw(ZIPException("Corrupted file!!!"));
		}

		size -= length;
		file.write(buffer,length);

	}while(size);

	file.close();

	if(zip_fclose(fileStream))
	{
		Throw(ZIPException("Unable to close file!!!"));
	}

	fileStream = NULL;

}

void ZipFile::extractAllFile(std::string dirPath)
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	std::vector<std::string> vec = this->listArchive();
	for(std::vector<std::string>::iterator i=vec.begin(); i!=vec.end(); i++)
		this->extractFile(*i,dirPath);
}

void ZipFile::print()
{
	if(!archive)
	{
		Throw(ZIPException("Invalid Zip archive!!!"));
	}

	int length = zip_get_num_files(archive);
	for(int i=0; i<length; i++)
		std::cout << zip_get_name(archive,i,ZIP_FL_UNCHANGED) << std::endl;
}

}
